﻿using System.Collections;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

using ThingsGateway.NewLife.Caching;

namespace ThingsGateway.SqlSugar
{
    public static class ExpressionBuilderHelper
    {

        public static object CallFunc(Type genericType, object[] param, object methodData, string methodName)
        {
            var type = methodData.GetType();
            var key = $"{type.FullName}.{methodName}<{genericType.FullName}>";

            var func = MemoryCache.Instance.GetOrAdd(key, _ => BuildDelegate(type, methodName, genericType));

            return func(methodData, param);
        }

        private static Func<object, object[], object> BuildDelegate(Type targetType, string methodName, Type genericType)
        {
            // 找到泛型方法定义
            var method = targetType.GetMethod(methodName);
            if (method == null)
                throw new MissingMethodException($"Method '{methodName}' not found on type '{targetType.FullName}'");

            var genericMethod = method.MakeGenericMethod(genericType);

            // 参数： (object target, object[] args)
            var targetParam = Expression.Parameter(typeof(object), "target");
            var argsParam = Expression.Parameter(typeof(object[]), "args");

            // 方法参数列表构造，将 object[] args 中的元素转换到具体参数类型
            var methodParams = genericMethod.GetParameters();
            Expression[] callParams = new Expression[methodParams.Length];

            for (int i = 0; i < methodParams.Length; i++)
            {
                var indexExpr = Expression.Constant(i);
                var paramAccessorExpr = Expression.ArrayIndex(argsParam, indexExpr);
                var paramCastExpr = Expression.Convert(paramAccessorExpr, methodParams[i].ParameterType);
                callParams[i] = paramCastExpr;
            }

            // 转换调用目标
            var instanceCast = Expression.Convert(targetParam, targetType);

            // 调用方法表达式
            var callExpr = Expression.Call(instanceCast, genericMethod, callParams);

            // 如果方法返回 void，返回 null，否则装箱返回值
            Expression body = genericMethod.ReturnType == typeof(void)
                ? Expression.Block(callExpr, Expression.Constant(null))
                : Expression.Convert(callExpr, typeof(object));

            // 创建 lambda (object target, object[] args) => ...
            var lambda = Expression.Lambda<Func<object, object[], object>>(body, targetParam, argsParam);

            return lambda.Compile();
        }


        /// <summary>
        /// Create Expression
        /// </summary>
        public static Expression CreateExpression(Expression left, Expression value, ExpressionType type)
        {
            if (type == ExpressionType.Equal)
            {
                return Expression.Equal(left, Expression.Convert(value, left.Type));
            }
            else if (type == ExpressionType.NotEqual)
            {
                return Expression.NotEqual(left, Expression.Convert(value, left.Type));
            }
            else
            {
                //Not implemented, later used in writing
                return Expression.Equal(left, Expression.Convert(value, left.Type));
            }
        }
        public static Expression CreateExpressionLike<ColumnType>(Type entityType, string propertyName, List<ColumnType> list)
        {
            var parameter = Expression.Parameter(entityType, "p");
            MemberExpression memberProperty = Expression.PropertyOrField(parameter, propertyName);
            MethodInfo method = typeof(List<>).MakeGenericType(typeof(ColumnType)).GetMethod(nameof(IList.Contains));
            ConstantExpression constantCollection = Expression.Constant(list);

            MethodCallExpression methodCall = Expression.Call(constantCollection, method, memberProperty);

            var expression = Expression.Lambda(methodCall, parameter);
            return expression;
        }
        public static Expression<Func<T, object>> CreateNewFields<T>(EntityInfo entity, List<string> propertyNames)
        {
            Type sourceType = typeof(T);
            Dictionary<string, PropertyInfo> sourceProperties = entity.Columns.Where(it => propertyNames.Contains(it.PropertyName)).ToDictionary(it => it.PropertyName, it => it.PropertyInfo);

            if (StaticConfig.EnableAot)
            {
                Type dynamicType = typeof(T);

                ParameterExpression sourceItem = Expression.Parameter(sourceType, "t");
                IEnumerable<MemberBinding> bindings = dynamicType.GetProperties().Where(it => sourceProperties.Any(s => s.Key == it.Name)).Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();

                return Expression.Lambda<Func<T, object>>(Expression.MemberInit(
                    Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);
            }
            else
            {
                Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);

                ParameterExpression sourceItem = Expression.Parameter(sourceType, "t");
                IEnumerable<MemberBinding> bindings = dynamicType.GetRuntimeProperties().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();

                return Expression.Lambda<Func<T, object>>(Expression.MemberInit(
                    Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);
            }
        }
        public static Expression CreateExpressionSelectField(Type classType, string propertyName, Type propertyType, string shortName = "it")
        {
            ParameterExpression parameter = Expression.Parameter(classType, shortName);

            // 创建属性表达式
            PropertyInfo propertyInfo = classType.GetProperty(propertyName);
            MemberExpression property = Expression.Property(parameter, propertyInfo);

            // 创建Lambda表达式
            Type funcType = typeof(Func<,>).MakeGenericType(classType, propertyType);
            LambdaExpression lambda = Expression.Lambda(funcType, property, parameter);
            return lambda;
        }
        public static Expression CreateExpressionSelectFieldObject(Type classType, string propertyName)
        {
            ParameterExpression parameter = Expression.Parameter(classType, "it");

            PropertyInfo propertyInfo = classType.GetProperty(propertyName);
            MemberExpression property = Expression.Property(parameter, propertyInfo);

            UnaryExpression convert = Expression.Convert(property, typeof(object));
            var funcType = typeof(Func<,>).MakeGenericType(classType, typeof(object));
            LambdaExpression lambda = Expression.Lambda(funcType, convert, parameter);
            return lambda;
        }
    }
    internal static class LinqRuntimeTypeBuilder
    {
        private static readonly AssemblyName AssemblyName = new AssemblyName() { Name = "LinqRuntimeTypes4iTheoChan" };
        private static readonly ModuleBuilder ModuleBuilder;
        private static readonly Dictionary<string, Type> BuiltTypes = new Dictionary<string, Type>();

        static LinqRuntimeTypeBuilder()
        {
            ModuleBuilder = AssemblyBuilder.DefineDynamicAssembly(AssemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(AssemblyName.Name);
        }

        private static string GetTypeKey(Dictionary<string, Type> properties)
        {
            //TODO: optimize the type caching -- if fields are simply reordered, that doesn't mean that they're actually different types, so this needs to be smarter
            string key = string.Empty;
            foreach (var prop in properties)
                key += $"{prop.Key};{prop.Value.Name};";

            return key;
        }

        private const MethodAttributes RuntimeGetSetAttrs = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

        public static Type BuildDynamicType(Dictionary<string, Type> properties)
        {
            ArgumentNullException.ThrowIfNull(properties);
            if (0 == properties.Count)
                throw new ArgumentOutOfRangeException(nameof(properties), "fields must have at least 1 field definition");

            try
            {
                // Acquires an exclusive lock on the specified object.
                Monitor.Enter(BuiltTypes);
                string className = GetTypeKey(properties);

                if (BuiltTypes.TryGetValue(className, out Type? value))
                    return value;

                TypeBuilder typeBdr = ModuleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class);

                foreach (var prop in properties)
                {
                    var propertyBdr = typeBdr.DefineProperty(name: prop.Key, attributes: PropertyAttributes.None, returnType: prop.Value, parameterTypes: null);
                    var fieldBdr = typeBdr.DefineField("itheofield_" + prop.Key, prop.Value, FieldAttributes.Private);

                    MethodBuilder getMethodBdr = typeBdr.DefineMethod("get_" + prop.Key, RuntimeGetSetAttrs, prop.Value, Type.EmptyTypes);
                    ILGenerator getIL = getMethodBdr.GetILGenerator();
                    getIL.Emit(OpCodes.Ldarg_0);
                    getIL.Emit(OpCodes.Ldfld, fieldBdr);
                    getIL.Emit(OpCodes.Ret);

                    MethodBuilder setMethodBdr = typeBdr.DefineMethod("set_" + prop.Key, RuntimeGetSetAttrs, null, new Type[] { prop.Value });
                    ILGenerator setIL = setMethodBdr.GetILGenerator();
                    setIL.Emit(OpCodes.Ldarg_0);
                    setIL.Emit(OpCodes.Ldarg_1);
                    setIL.Emit(OpCodes.Stfld, fieldBdr);
                    setIL.Emit(OpCodes.Ret);

                    propertyBdr.SetGetMethod(getMethodBdr);
                    propertyBdr.SetSetMethod(setMethodBdr);
                }

                BuiltTypes[className] = typeBdr.CreateType();

                return BuiltTypes[className];
            }
            catch
            {
                throw;
            }
            finally
            {
                Monitor.Exit(BuiltTypes);
            }
        }

        private static string GetTypeKey(IEnumerable<PropertyInfo> properties)
        {
            return GetTypeKey(properties.ToDictionary(f => f.Name, f => f.PropertyType));
        }

        public static Type GetDynamicType(IEnumerable<PropertyInfo> properties)
        {
            return BuildDynamicType(properties.ToDictionary(f => f.Name, f => f.PropertyType));
        }
    }
}